﻿using System;
using System.Collections;
using System.Data.Linq;
using System.Diagnostics;
using System.Linq;
using System.Text;
using DVK.SP2007.Constants;
using DVK.SP2007.UniversalClient;
using DVK.SP2007.UniversalClient.DataAccess;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.IO;
using System.Collections.Generic;

namespace DVK.SP2007
{
    /// <summary>
    /// SendReceiveJob is a Sharepoint TimerJob which automatically:
    ///     Sends messages in the outbox list to universal clients database.
    ///     Updates sent messages statused in the outbox list.
    ///     Receives messages from universal client database.
    /// </summary>
    public class SendReceiveJob : SPJobDefinition
    {
        private IDvkResourceManager _resourceManager;
        private Logger _logger;

        /// <summary>
        /// Initializes a new instance of the <see cref="SendReceiveJob"/> class.
        /// </summary>
        public SendReceiveJob()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SendReceiveJob"/> class.
        /// </summary>
        /// <param name="jobName">Name of the job.</param>
        /// <param name="service">The service.</param>
        /// <param name="server">The server.</param>
        /// <param name="lockType">Type of the lock.</param>
        public SendReceiveJob(string jobName, SPService service, SPServer server, SPJobLockType lockType)
            : base(jobName, service, server, lockType)
        {
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="SendReceiveJob"/> class.
        /// </summary>
        /// <param name="jobName">Name of the job.</param>
        /// <param name="webApplication">The web application.</param>
        public SendReceiveJob(string jobName, SPWebApplication webApplication)
            : base(jobName, webApplication, null, SPJobLockType.Job)
        {

        }

        /// <summary>
        /// Executes the job definition.
        /// 
        /// Starts the send/receive process.
        /// </summary>
        /// <param name="targetInstanceId">For target types of <see cref="T:Microsoft.SharePoint.Administration.SPContentDatabase"></see> this is the database ID of the content database being processed by the running job. This value is Guid.Empty for all other target types.</param>
        public override void Execute(System.Guid targetInstanceId)
        {
            this._logger = JobHelper.GetLoggerForSPJob(LoggingSources.SendReceiveJob, this.Properties);
            this._logger.LogInformative("{0}: Logger is logging on {1} level.", LoggingSources.SendReceiveJob, _logger.Switch.Level.ToString());
            
            Stopwatch watcher = new Stopwatch();
            watcher.Start();

            base.Execute(targetInstanceId);

            _resourceManager = new JobsResourceManager();

            try
            {
                var siteUrl = this.Properties[Settings.DvkSiteUrl] as string;

                using (SPSite site = new SPSite(siteUrl))
                using (SPWeb web = site.RootWeb)
                {
                    _logger.LogInformative("{0}: Started job.", web.Url);

                    string connectionString = new DvkSettingsRepository(web).QuerySetting(Settings.DvkUKDatabaseConnectionString);

                    if (connectionString == null)
                    {
                        _logger.LogError(
                            LoggerEvents.ConnectionStringIsNotConfigured,
                            "{0}: ConnectionString is null.",
                            web.Url
                            );
                        return;
                    }

                    UpdateSentDocuments(web, connectionString);
                    ReceiveDocuments(web, connectionString);
                    SendDocuments(web, connectionString);
                }
            }
            catch (Exception exc)
            {
                _logger.LogError(
                    LoggerEvents.UnhandledExceptionEncountered,
                    exc,
                    "SendReceive was interrupted."
                    );
            }
            finally
            {
                watcher.Stop();
                _logger.LogInformative(
                        "Job last runtime: {0} ms.",
                        watcher.ElapsedMilliseconds
                    );
            }
        }


        /// <summary>
        /// Takes messages from DVK universal client database and creates listitems in DvkInbox list.
        /// <remarks>
        /// Only messages, which are incoming and have local_item_id null or 0, are taken.
        /// </remarks>
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="connectionString">The connection string.</param>
        private void ReceiveDocuments(SPWeb web, string connectionString)
        {
            _logger.LogInformative(
                "{0}:ReceiveDocuments started",
                web.Url
                );
            //Getting inboxList
            SPList inboxList = web.GetListFromUrl(Constants.Urls.ListInbox);

            var contentTypeId = inboxList.ContentTypes.BestMatch(
                    new SPContentTypeId(ContentTypes.InboxDocument.ID)
                );

            using (var universalClient = new UniversalClientDao(connectionString))
            {
                IList<IDvkMessage> incomingMessages = new List<IDvkMessage>();

                try
                {
                    incomingMessages = universalClient.GetIncomingMessages();
                }
                catch (Exception exc)
                {
                    _logger.LogError(
                        LoggerEvents.UnhandledExceptionEncountered,
                        "{0}: Failed to get incoming messages for processing. Exception details: {1}.",
                        web.Url,
                        exc.ToString()
                    );
                    return; //No use processing!
                }

                _logger.LogInformative(
                        "{0}: Incoming messages to process: {1}.",
                        web.Url,
                        incomingMessages.Count
                    );

                foreach (var dvkMessage in incomingMessages)
                {
                    SPListItem newItem = null;
                    try
                    {
                        //NOTE: Not in use now. Only numerators are used now.
                        ////Before adding new file, check if one exists already and move it to archive if possible.
                        //MessageArchiver.CheckInboxAndMoveToHistory(web, targetUrl);
                        var targetUrl = InboxOutboxItemHelper.GetValidTargetUrl(web, inboxList, dvkMessage.Title);

                        var properties = new Hashtable
                                         {
                                             {Constants.FieldNames.SPFileContentTypeIdProperty, contentTypeId.ToString()}
                                         };

                        //Add new file
                        SPFile newFile = inboxList.RootFolder.Files.Add(targetUrl, dvkMessage.DataXml.SignedDocInBytes, properties, true);

                        //Map metadata to the new item
                        newItem = newFile.Item;
                        newItem = new IncomingMessageToListitemMapper(dvkMessage, newItem, _resourceManager).Map();
                        newItem.UpdateOverwriteVersion();

                        //Update the message with local item id (newitem id).
                        universalClient.UpdateMessageLocalItemReference(dvkMessage, newItem.ID);
                    }
                    catch (Exception exc)
                    {
                        _logger.LogError(
                            LoggerEvents.ReceiveDocumentFailed,
                            exc,
                            "{0}: DvkMessageId={1}, Title={2}.",
                            web.Url,
                            dvkMessage.Id,
                            dvkMessage.Title
                            );

                        //If receiving failed, delete the item so partial items wouldnt be left behind
                        if (newItem != null)
                            newItem.Delete();
                        universalClient.UpdateMessageLocalItemReference(dvkMessage, 0);
                    }
                }
            }

            _logger.LogInformative("{0}: ReceiveDocuments ended.", web.Url);
        }

        /// <summary>
        /// Takes listitems from Outbox list and creates dhl messages based on listitem in the universal client database. 
        /// <remarks>
        /// Only listitems with ToBeProcessed field value as True will be taken.
        /// Only 100 items at a time are processed.
        /// </remarks>
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="connectionstring">The connectionstring.</param>
        private void SendDocuments(SPWeb web, string connectionstring)
        {
            _logger.LogInformative("{0}: SendDocuments started.", web.Url);
            SPList outboxList = web.GetListFromUrl(Constants.Urls.ListOutbox);

            var query = new SPQuery();
            query.RowLimit = 100;
            query.Query = "<Where>" +
            "<Eq><FieldRef Name='" + Fields.ToBeProcessed.Name + "' /><Value Type='Boolean'>" + 1 + "</Value></Eq>" +
            "</Where>";

            SPListItemCollection itemsToProcess = outboxList.GetItems(query);

            _logger.LogInformative("{0}: Items to send: {1}.", web.Url, itemsToProcess.Count);

            //If there are no items to process, then dont process
            if (itemsToProcess.Count == 0)
                return;

            //The listitem already contains the XML parts required to make up the message. 
            foreach (SPListItem item in itemsToProcess)
            {
                try
                {
                    using (var universalClient = new UniversalClientDao(connectionstring))
                    {
                        IDvkMessage message = new OutgoingMessageToDvkMessageMapper(item, _resourceManager).Map();//outboxMessageConverter.ToDvkMessage(item);

                        universalClient.AddMessage(message);

                        _logger.LogInformative("{0}: Message added for sending. Message Id: {1}", web.Url, message.Id);

                        //If processed, then set the flag to 0.
                        //also update the statuses
                        item[Fields.UkMessageId.Name] = message.Id;
                        item[Fields.SendingStatus.Name] = (int)message.SendingStatus;
                        item[Fields.SendingStatusText.Name] =
                            _resourceManager.GetResourceBasedOnEnumElement<DvkSendingStatus>(message.SendingStatus);
                        item[Fields.ToBeProcessed.Name] = false;
                        item[Fields.StatusUpdateNeeded.Name] = true;
                        item.Update();
                    }
                }
                catch (Exception exc)
                {
                    _logger.LogError(
                        LoggerEvents.SendDocumentFailed,
                        exc,
                        "{0}: Item sending failed (ItemId={1}, Name={2}).",
                        web.Url,
                        item.ID,
                        item.Name
                        );
                }
            }
            _logger.LogInformative("{0}: SendDocuments ended.", web.Url);

        }


        /// <summary>
        /// Updates the listitems in the outbox list which have already been sent to DVK.
        /// <remarks>
        /// Only items with StatusUpdateNeeded = True are processed.
        /// </remarks>
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="connectionString">The connection string.</param>
        private void UpdateSentDocuments(SPWeb web, string connectionString)
        {
            _logger.LogInformative("{0}: UpdateSentDocuments started.", web.Url);
            SPList outboxList = web.GetListFromUrl(Constants.Urls.ListOutbox);

            var query = new SPQuery();
            query.RowLimit = 100;
            query.Query = "<Where>" +
            "<Eq><FieldRef Name='" + Fields.StatusUpdateNeeded.Name + "' /><Value Type='Boolean'>" + 1 + "</Value></Eq>" +
            "</Where>";

            SPListItemCollection itemsToProcess = outboxList.GetItems(query);

            if (itemsToProcess.Count == 0)
            {
                return;
            }

            foreach (SPListItem item in itemsToProcess)
            {
                int? messageId = -1; //No such message
                try
                {
                    messageId = item[Fields.UkMessageId.Name] as int?;

                    //Skip
                    if (!messageId.HasValue || (messageId.HasValue && messageId.Value == -1))
                    {
                        _logger.LogInformative(
                            "{0}: UpdateSentDocuments skipped because items message id was missing (-1 or null). Item id: {1}, name: {2}.", 
                            web.Url,
                            item.ID,
                            item.Name
                            );
                        continue;
                    }

                    DvkSendingStatus sendingStatus;
                    DvkFault fault;
                    bool itemChanged = false;

                    using (var universalClient = new UniversalClientDao(connectionString))
                    {
                        universalClient.UpdateSentMessageStatus(messageId.Value, out sendingStatus, out fault);
                    }

                    //Update information on the sent message in SharePoint
                    string oldFault = item[Fields.Fault.Name] as string;
                    string newFault = (fault != null ? fault.ToString() : string.Empty);

                    _logger.LogInformative(
                        "{0}: UpdateSentDocuments - new sendingstatus: '{1}', '{2}'; new fault: '{3}'.",
                        web.Url,
                        (int)sendingStatus,
                        sendingStatus.ToString(),
                        newFault);

                    if ((!string.IsNullOrEmpty(newFault) && !string.IsNullOrEmpty(oldFault) && !oldFault.Equals(newFault))
                        || (!string.IsNullOrEmpty(newFault) && string.IsNullOrEmpty(oldFault)))
                    {
                        item[Fields.Fault.Name] = newFault;
                        itemChanged = true;
                    }
                    int? oldSendingStatus = item[Fields.SendingStatus.Name] as int?;
                    int newSendingStatus = (int)sendingStatus;
                    if ((oldSendingStatus.HasValue && oldSendingStatus.Value != newSendingStatus) || !oldSendingStatus.HasValue)
                    {
                        item[Fields.SendingStatus.Name] = (int)sendingStatus;
                        item[Fields.SendingStatusText.Name] = _resourceManager.GetResourceBasedOnEnumElement<DvkSendingStatus>(sendingStatus);
                        itemChanged = true;
                    }

                    if (sendingStatus == DvkSendingStatus.Sent)
                    {
                        item[Fields.StatusUpdateNeeded.Name] = false;
                        itemChanged = true;
                    }

                    if (itemChanged)
                    {
                        item.Update();
                    }
                }
                catch (Exception exc)
                {
                    _logger.LogError(
                        LoggerEvents.UpdateDocumentStatusFailed,
                        exc,
                        "{0}: Sent item status updating failed (ItemId={1}, Name={2}, Message id: {3}).",
                        web.Url,
                        item.ID,
                        item.Name,
                        messageId.Value
                        );
                }
            }
            _logger.LogInformative("{0}: UpdateSentDocuments ended.", web.Url);
        }
    }
}